package org.rms.cayenne.access;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.cayenne.DataObjectUtils;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.QueryResponse;
import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.query.EJBQLQuery;
import org.apache.cayenne.query.Ordering;
import org.apache.cayenne.query.PrefetchTreeNode;
import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.query.SortOrder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.rms.model.access.Persistable;
import org.rms.model.access.PersistableManager;

public class CayenneObjectManager<T extends Persistable> implements PersistableManager<T> {

    private static final Log logger = LogFactory.getLog(CayenneObjectManager.class);

    private String contextInfo;
    private Class<T> objectClass;
    private Map<String, Object> baseNameValuePairs = Collections.emptyMap();

	private Ordering sortCriterium;

    public CayenneObjectManager(Class<T> objectClass) {
        this.objectClass = objectClass;
    }

    public T getObject(int id) {
        T object = getObjectOrNull(id);
        if (object == null) {
            throw new IllegalArgumentException(getObjectClass().getSimpleName() + " " + id
                    + " not found.");
        }
        return object;
    }

    protected T getObjectOrNull(int id) {
        return DataObjectUtils.objectForPK(getObjectContext(), getObjectClass(), id);
    }

    protected String getPrimaryKeyAttribute() {
        return "id";
    }

    public Class<T> getObjectClass() {
        return this.objectClass;
    }

    /**
     * @return A list of expressions limiting the result set.
     */
    protected final List<Expression> getBaseExpressions() {
        final List<Expression> expressions = new ArrayList<Expression>();
        final Map<String, Object> pairs = getBaseNameValuePairs();
        for (final String key : pairs.keySet()) {
            expressions.add(ExpressionFactory.matchExp(key, pairs.get(key)));
        }
        return expressions;
    }

    /**
     * @return A list of name-value pairs limiting the result set. Can be used for access control
     *         purposes.
     */
    protected Map<String, Object> getBaseNameValuePairs() {
        return this.baseNameValuePairs;
    }

    protected ObjAttribute getAttribute(ObjEntity entity, String path) {
        final int dotIndex = path.indexOf('.');
        if (dotIndex < 0) {
            return (ObjAttribute) entity.getAttribute(path);
        } else {
            final String relName = path.substring(0, dotIndex);
            final String attrPath = path.substring(dotIndex + 1);
            final ObjEntity relTarget = (ObjEntity) entity.getRelationship(relName)
                    .getTargetEntity();
            if (relTarget == null) {
                throw new IllegalArgumentException("Cant resolve relationship " + entity.getName()
                        + "." + relName + ".");
            }
            return getAttribute(relTarget, attrPath);
        }
    }

    protected ObjEntity getObjectEntity() {
        return getObjectContext().getEntityResolver()
                .getObjEntity(getObjectClass().getSimpleName());
    }

    public List<T> getObjects() {
        final List<Expression> expressions = new ArrayList<Expression>(getBaseExpressions());
        return getObjects(expressions);
    }

    public void delete(T object) {
        getObjectContext().deleteObject(object);
    }

    public T newObject() {
        return getObjectContext().newObject(getObjectClass());
    }

    public List<T> getObjects(String attrName, Object value) {
        List<Expression> expressions = new ArrayList<Expression>(getBaseExpressions());
        expressions.add(ExpressionFactory.matchExp(attrName, value));
        return getObjects(expressions);
    }

    protected List<T> getObjects(List<Expression> expressions) {
        final SelectQuery query = new SelectQuery(getObjectClass(), ExpressionFactory.joinExp(
                Expression.AND, expressions));
        final Ordering sortCriterium = getSortCriterium();
        if (sortCriterium != null) {
            query.addOrdering(sortCriterium.getSortSpecString(),
                    sortCriterium.isAscending() ? SortOrder.ASCENDING : SortOrder.DESCENDING);
        }
        addPrefetchPaths(query);
        return getObjectContext().performQuery(query);
    }

    protected void addPrefetchPaths(final SelectQuery query) {
        for (final String prefetch : getPrefetchPaths()) {
            query.addPrefetch(prefetch).setSemantics(PrefetchTreeNode.JOINT_PREFETCH_SEMANTICS);
        }
    }

    public List<T> getObjects(Map<String, Object> nameValuePairs) {
        List<Expression> expressions = new ArrayList<Expression>(getBaseExpressions());
        for (final String key : nameValuePairs.keySet()) {
            expressions.add(ExpressionFactory.matchExp(key, nameValuePairs.get(key)));
        }
        return getObjects(expressions);
    }

    public void setContextInfo(String contextInfo) {
        this.contextInfo = contextInfo;
    }

    public String getContextInfo() {
        return this.contextInfo;
    }

    public ObjectContext getObjectContext() {
        return CayenneRepository.getCayenneSession().getObjectContext();
    }

    protected Ordering getSortCriterium() {
        return this.sortCriterium;
    }

    public void setSortCriterium(Ordering sortCriterium) {
        this.sortCriterium = sortCriterium;
    }

    public void setBaseFilters(Map<String, Object> pairs) {
        this.baseNameValuePairs = pairs;
    }

    protected List<String> getPrefetchPaths() {
        return new ArrayList<String>();
    }

    public boolean containsObject(int id) {
        return getObjectOrNull(id) != null;
    }

}
